{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Materials\n",
    "\n",
    "Materials are the primary container for radionuclides. They map nuclides to **mass weights**,\n",
    "though they contain methods for converting to/from atom fractions as well.\n",
    "In many ways they take inspiration from numpy arrays and python dictionaries.  Materials\n",
    "have two main attributes which define them.\n",
    "\n",
    "1. **comp**: a normalized composition mapping from nuclides (zzaaam-ints) to mass-weights (floats).\n",
    "1. **mass**: the mass of the material.\n",
    "\n",
    "By keeping the mass and the composition separate, operations that only affect one attribute\n",
    "may be performed independent of the other.  Additionally, most of the functionality is\n",
    "implemented in a C++ class by the same name, so this interface is very fast and light-weight.\n",
    "Materials may be initialized in a number of different ways.  For example, initializing from\n",
    "dictionaries of compositions are shown below."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 1,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "pyne.material.Material({922350000: 0.04, 922380000: 0.96}, 42.0, -1.0, -1.0, {})"
      ]
     },
     "execution_count": 1,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "from pyne.material import Material\n",
    "\n",
    "leu = Material({'U238': 0.96, 'U235': 0.04}, 42)\n",
    "leu"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Material:\n",
      "mass = 9.0\n",
      "density = -1.0\n",
      "atoms per molecule = -1.0\n",
      "-------------------------\n",
      "H1     0.1111111111111111\n",
      "O16    0.1111111111111111\n",
      "Tm169  0.1111111111111111\n",
      "U235   0.1111111111111111\n",
      "U238   0.1111111111111111\n",
      "Pu239  0.1111111111111111\n",
      "Pu241  0.1111111111111111\n",
      "Am242  0.1111111111111111\n",
      "Cm244  0.1111111111111111\n"
     ]
    }
   ],
   "source": [
    "nucvec = {10010:  1.0, 80160:  1.0, 691690: 1.0, 922350: 1.0,\n",
    "          922380: 1.0, 942390: 1.0, 942410: 1.0, 952420: 1.0,\n",
    "          962440: 1.0}\n",
    "mat = Material(nucvec)\n",
    "print(mat)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Normalization\n",
    "\n",
    "Materials may also be initialized from plain text or HDF5 files (see ``Material.from_text`` and\n",
    "``Material.from_hdf5``).  Once you have a Material instance, you can always obtain the unnormalized\n",
    "mass vector through ``Material.mult_by_mass``.  Normalization routines to normalize the mass \n",
    "``Material.normalize`` or the composition ``Material.norm_comp`` are also available."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "{922350000: 1.68, 922380000: 40.32}"
      ]
     },
     "execution_count": 3,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "leu.mult_by_mass()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "{10010000: 0.1111111111111111, 80160000: 0.1111111111111111, 691690000: 0.1111111111111111, 922350000: 0.1111111111111111, 922380000: 0.1111111111111111, 942390000: 0.1111111111111111, 942410000: 0.1111111111111111, 952420000: 0.1111111111111111, 962440000: 0.1111111111111111}"
      ]
     },
     "execution_count": 4,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "mat.normalize()\n",
    "mat.mult_by_mass()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "1.0"
      ]
     },
     "execution_count": 5,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "mat.mass"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Material Arithmetic\n",
    "\n",
    "Furthermore, various arithmetic operations between Materials and numeric types are also defined.\n",
    "Adding two Materials together will return a new Material whose values are the weighted union\n",
    "of the two original. Multiplying a Material by 2, however, will simply double the mass."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 6,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "pyne.material.Material({10010000: 0.11111111111111108, 80160000: 0.11111111111111108, 691690000: 0.11111111111111108, 922350000: 0.11111111111111108, 922380000: 0.11111111111111108, 942390000: 0.11111111111111108, 942410000: 0.11111111111111108, 952420000: 0.11111111111111108, 962440000: 0.11111111111111108}, 2.0, -1.0, -1.0, {})"
      ]
     },
     "execution_count": 6,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "other_mat = mat * 2\n",
    "other_mat"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 7,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "2.0"
      ]
     },
     "execution_count": 7,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "other_mat.mass"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 8,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Material:\n",
      "mass = 60.0\n",
      "density = -1.0\n",
      "atoms per molecule = -1.0\n",
      "-------------------------\n",
      "H1     0.03333333333333332\n",
      "O16    0.03333333333333332\n",
      "Tm169  0.03333333333333332\n",
      "U235   0.06133333333333332\n",
      "U238   0.7053333333333334\n",
      "Pu239  0.03333333333333332\n",
      "Pu241  0.03333333333333332\n",
      "Am242  0.03333333333333332\n",
      "Cm244  0.03333333333333332\n"
     ]
    }
   ],
   "source": [
    "weird_mat = leu + mat * 18\n",
    "print(weird_mat)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Raw Member Access\n",
    "\n",
    "You may also change the attributes of a material directly without generating a new \n",
    "material instance."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 9,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Material:\n",
      "mass = 10.0\n",
      "density = -1.0\n",
      "atoms per molecule = -1.0\n",
      "-------------------------\n",
      "H2     3.0\n",
      "U235   15.0\n"
     ]
    }
   ],
   "source": [
    "other_mat.mass = 10\n",
    "other_mat.comp = {10020: 3, 922350: 15.0}\n",
    "print(other_mat)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Of course when you do this you have to be careful because the composition and mass may now be out\n",
    "of sync.  This may always be fixed with normalization."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 10,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Material:\n",
      "mass = 10.0\n",
      "density = -1.0\n",
      "atoms per molecule = -1.0\n",
      "-------------------------\n",
      "H2     0.16666666666666666\n",
      "U235   0.8333333333333334\n"
     ]
    }
   ],
   "source": [
    "other_mat.norm_comp()\n",
    "print(other_mat)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Indexing & Slicing\n",
    "Additionally (and very powerfully!), you may index into either the material or the composition \n",
    "to get, set, or remove sub-materials.  Generally speaking, the composition you may only index \n",
    "into by integer-key and only to retrieve the normalized value.  Indexing into the material allows the \n",
    "full range of operations and returns the unnormalized mass weight.  Moreover, indexing into\n",
    "the material may be performed with integer-keys, string-keys, slices, or sequences of nuclides."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 11,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "0.04"
      ]
     },
     "execution_count": 11,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "leu.comp[922350000]"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 12,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "1.68"
      ]
     },
     "execution_count": 12,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "leu['U235']"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 13,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "pyne.material.Material({922350000: 0.07359999999999998, 922380000: 0.8464, 942390000: 0.03999999999999998, 942410000: 0.03999999999999998}, 50.0, -1.0, -1.0, {})"
      ]
     },
     "execution_count": 13,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "weird_mat['U':'Am']"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 14,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Material:\n",
      "mass = 84.0\n",
      "density = -1.0\n",
      "atoms per molecule = -1.0\n",
      "-------------------------\n",
      "H2     0.5\n",
      "U235   0.5\n"
     ]
    }
   ],
   "source": [
    "other_mat[:920000000] = 42.0\n",
    "print(other_mat)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 15,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "pyne.material.Material({10010000: 0.16666666666666663, 922350000: 0.16666666666666663, 922380000: 0.16666666666666663, 942390000: 0.16666666666666663, 942410000: 0.16666666666666663, 952420000: 0.16666666666666663}, 0.6666666666666667, -1.0, -1.0, {})"
      ]
     },
     "execution_count": 15,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "del mat[962440, 'TM169', 'Zr90', 80160]\n",
    "mat[:]"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Other methods also exist for obtaining commonly used sub-materials, such as gathering the Uranium or \n",
    "Plutonium vector.  "
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Molecular Mass & Atom Fractions\n",
    "\n",
    "You may also calculate the molecular mass of a material via the ``Material.molecular_mass`` method.\n",
    "This uses the ``pyne.data.atomic_mass`` function to look up the atomic mass values of\n",
    "the constituent nuclides."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 16,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "237.9290363047951"
      ]
     },
     "execution_count": 16,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "leu.molecular_mass()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Note that by default, materials are assumed to have one atom per molecule.  This is a poor\n",
    "assumption for more complex materials.  For example, take water.  Without specifying the \n",
    "number of atoms per molecule, the molecular mass calculation will be off by a factor of 3.\n",
    "This can be remedied by passing the correct number to the method.  If there is no other valid\n",
    "number of molecules stored on the material, this will set the appropriate attribute on the \n",
    "class."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 17,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "6.003521561386799"
      ]
     },
     "execution_count": 17,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "h2o = Material({10010000: 0.11191487328808077, 80160000: 0.8880851267119192})\n",
    "h2o.molecular_mass()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 18,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "3.0"
      ]
     },
     "execution_count": 18,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "h2o.molecular_mass(3.0)\n",
    "h2o.atoms_per_molecule"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "It is often also useful to be able to convert the current mass-weighted material to \n",
    "an atom fraction mapping.  This can be easily done via the :meth:`Material.to_atom_frac`\n",
    "method.  Continuing with the water example, if the number of atoms per molecule is \n",
    "properly set then the atom fraction return is normalized to this amount.  Alternatively, \n",
    "if the atoms per molecule are set to its default state on the class, then a truly \n",
    "fractional number of atoms is returned."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 19,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "{10010000: 1.9999999999946356, 80160000: 1.0000000000053646}"
      ]
     },
     "execution_count": 19,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "h2o.to_atom_frac()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 20,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "{10010000: 0.6666666666648785, 80160000: 0.3333333333351215}"
      ]
     },
     "execution_count": 20,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "h2o.atoms_per_molecule = -1.0\n",
    "h2o.to_atom_frac()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Additionally, you may wish to convert the an existing set of atom fractions to a \n",
    "new material stream.  This can be done with the :meth:`Material.from_atom_frac` method, \n",
    "which will clear out the current contents of the material's composition and replace\n",
    "it with the mass-weighted values.  Note that \n",
    "when you initialize a material from atom fractions, the sum of all of the atom fractions\n",
    "will be stored as the atoms per molecule on this class.  Additionally, if a mass is not \n",
    "already set on the material, the molecular mass will be used."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 21,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "{10010000: 0.11191487328888054, 80160000: 0.8880851267111195}\n",
      "3.0\n",
      "18.01056468408\n",
      "18.01056468408\n"
     ]
    }
   ],
   "source": [
    "h2o_atoms = {10010: 2.0, 'O16': 1.0}\n",
    "h2o = Material()\n",
    "h2o.from_atom_frac(h2o_atoms)\n",
    "\n",
    "print(h2o.comp)\n",
    "print(h2o.atoms_per_molecule)\n",
    "print(h2o.mass)\n",
    "print(h2o.molecular_mass())"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Moreover, other materials may also be used to specify a new material from atom fractions.\n",
    "This is a typical case for reactors where the fuel vector is convolved inside of another \n",
    "chemical form.  Below is an example of obtaining the Uranium-Oxide material from Oxygen\n",
    "and low-enriched uranium."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 22,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Material:\n",
      "mass = 269.9188655439951\n",
      "density = -1.0\n",
      "atoms per molecule = 3.0\n",
      "------------------------\n",
      "O16    0.11851646299241672\n",
      "U235   0.03525934148030333\n",
      "U238   0.84622419552728\n"
     ]
    }
   ],
   "source": [
    "uox = Material()\n",
    "uox.from_atom_frac({leu: 1.0, 'O16': 2.0})\n",
    "print(uox)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "**NOTE:** Materials may be used as keys in a dictionary because they are hashable."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### User-defined Metadata\n",
    "\n",
    "Materials also have an ``attrs`` attribute which allows users to store arbitrary \n",
    "custom information about the material.  This can include things like units, comments, \n",
    "provenance information, or anything else the user desires.  This is implemented as an\n",
    "in-memory JSON object attached to the C++ class.  Therefore, what may be stored in\n",
    "the ``attrs`` is subject to the same restrictions as JSON itself.  The top-level \n",
    "of the attrs *should* be a dictionary, though this is not explicitly enforced."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 23,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "pyne.material.Material({922350000: 0.05, 922380000: 0.95}, 15.0, -1.0, -1.0, {})"
      ]
     },
     "execution_count": 23,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "leu = Material({922350: 0.05, 922380: 0.95}, 15, attrs={'units': 'kg'})\n",
    "leu"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 24,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Material:\n",
      "mass = 15.0\n",
      "density = -1.0\n",
      "atoms per molecule = -1.0\n",
      "-------------------------\n",
      "U235   0.05\n",
      "U238   0.95\n"
     ]
    }
   ],
   "source": [
    "print(leu)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 25,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "{}"
      ]
     },
     "execution_count": 25,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "leu.metadata"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 26,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "{\"comments\":[\"Anthony made this material.\",\"And then Katy made it better!\"],\"id\":42}"
      ]
     },
     "execution_count": 26,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "m = leu.metadata\n",
    "m['comments'] = ['Anthony made this material.']\n",
    "leu.metadata['comments'].append('And then Katy made it better!')\n",
    "m['id'] = 42\n",
    "leu.metadata"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 27,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "{\"units\":\"solar mass\"}"
      ]
     },
     "execution_count": 27,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "leu.metadata = {'units': 'solar mass'}\n",
    "leu.metadata"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 28,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "{\"units\":\"solar mass\"}"
      ]
     },
     "execution_count": 28,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "m"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 29,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "'not solar masses'"
      ]
     },
     "execution_count": 29,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "leu.metadata['units'] = 'not solar masses'\n",
    "leu.metadata['units']"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "As you can see from the above, the attrs interface provides a view into the underlying \n",
    "JSON object.  This can be manipulated directly or by renaming it to another variable.\n",
    "Additionally, ``attrs`` can be replaced with a new object of the appropriate type. \n",
    "Doing so invalidates any previous views into this container."
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.6.9"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 1
}
